using System;
using System.Runtime.Serialization;

namespace DanielSig
{
	public static class ByteManager
	{
		/*public static void PrepareFor(ref byte[] bytes, uint bitIndex, System.Type type)
		{
			PrepareFor(ref bytes, bitIndex, (sizeof(type) << 3));
		}
		public static void PrepareFor(ref byte[] bytes, uint bitIndex, System.Type type, int amount)
		{
			PrepareFor(ref bytes, bitIndex, (sizeof(type) << 3) * amount);
		}*/
		public static void PrepareFor(ref byte[] bytes, uint bitIndex, int numberOfBitsPerValue, int amount)
		{
			PrepareFor(ref bytes, bitIndex, numberOfBitsPerValue * amount);
		}
		public static byte[] Init(int numberOfBits)
		{
			int minRequiredLength = ( (numberOfBits - 1) >> 3 ) + 1;
			return new byte[minRequiredLength];
		}
		public static void PrepareFor(ref byte[] bytes, uint bitIndex, int numberOfBits)
		{
			int minRequiredLength = ( ((int)bitIndex + numberOfBits - 1) >> 3 ) + 1;
			if(minRequiredLength > bytes.Length)
			{
				Array.Resize<byte>(ref bytes, minRequiredLength);
			}
		}
		public static void Finish(ref byte[] bytes, uint bitIndex)
		{
			int preferableLength = (int)((bitIndex - 1) >> 3) + 1;
			if(preferableLength < bytes.Length)
			{
				Array.Resize<byte>(ref bytes, preferableLength);
			}
		}
	}
	public static class XByteArray
	{
		
		#region Writes
		public static void WriteBits(this byte[] bytes, byte[] values, ref uint bitIndex)
		{
			int numBytes = values.Length;
			for(int i = 0; i < numBytes; i++)
			{
				bytes.WriteByte(values[i], ref bitIndex);
			}
		}
		public static void WriteBits(this byte[] bytes, byte[] values, ref uint bitIndex, int amount)
		{
			if(amount == 0) throw new ArgumentOutOfRangeException("amount", "amount must be 1 or greater. But it is: " + amount);
			int numBytes = 1 + ((amount - 1) >> 3);
			numBytes--;
			for(int i = 0; i < numBytes; i++)
			{
				bytes.WriteByte(values[i], ref bitIndex);
			}
			amount &= 0x7;
			if(amount == 0) amount = 8; 
			bytes.WriteByte(values[numBytes], ref bitIndex, amount);
		}
		public static void WriteIntArray(this byte[] bytes, int[] values, int numBitsPerInt = 32)
		{
			uint bitIndex = 0;
			bytes.WriteIntArray(values, ref bitIndex, values.Length, numBitsPerInt);
		}
		public static void WriteIntArray(this byte[] bytes, int[] values, ref uint bitIndex, int amount, int numBitsPerInt = 32)
		{
			for(int i = 0; i < amount; i++)
			{
				int valueInstance = values[i];
				bytes.Write(BitConverter.ToUInt32(BitConverter.GetBytes(valueInstance), 0), ref bitIndex, numBitsPerInt);
			}
		}
		public static void WriteInt(this byte[] bytes, int value, ref uint bitIndex, int amount = 32)
		{
			bytes.Write(BitConverter.ToUInt32(BitConverter.GetBytes(value), 0), ref bitIndex, amount);
		}
		public static void WriteUInt(this byte[] bytes, uint value, ref uint bitIndex, int amount = 32)
		{
			bytes.Write(BitConverter.ToUInt32(BitConverter.GetBytes(value), 0), ref bitIndex, amount);
		}
		public static void WriteFloat(this byte[] bytes, float value, ref uint bitIndex)
		{
			bytes.Write(BitConverter.ToUInt32(BitConverter.GetBytes(value), 0), ref bitIndex, 32);
		}
		public static void WriteShort(this byte[] bytes, short value, ref uint bitIndex, int amount = 16)
		{
			bytes.Write((uint)BitConverter.ToUInt16(BitConverter.GetBytes(value), 0), ref bitIndex, amount);
		}
		public static void WriteUshort(this byte[] bytes, ushort value, ref uint bitIndex, int amount = 16)
		{
			bytes.Write((uint)value, ref bitIndex, amount);
		}
		public static void WriteDouble(this byte[] bytes, double value, ref uint bitIndex)
		{
			uint bitIndex2 = 0;
			byte[] bits = BitConverter.GetBytes(value);
			bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex, 32);
			bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex, 32);
		}
		public static void WriteLong(this byte[] bytes, long value, ref uint bitIndex, int amount = 64)
		{
			uint bitIndex2 = 0;
			byte[] bits = BitConverter.GetBytes(value);
			bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex, amount.Min(32));
			if(amount > 32) bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex, amount - 32);
		}
		public static void WriteUlong(this byte[] bytes, ulong value, ref uint bitIndex, int amount = 64)
		{
			uint bitIndex2 = 0;
			byte[] bits = BitConverter.GetBytes(value);
			bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex,  amount.Min(32));
			if(amount > 32) bytes.Write(bits.ReadUint(ref bitIndex2), ref bitIndex, amount - 32);
		}
		public static void WriteBool(this byte[] bytes, bool value, ref uint bitIndex)
		{
			bytes.Write((uint)(value ? 1 : 0), ref bitIndex, 1);
		}
		public static void WriteByte(this byte[] bytes, byte value, ref uint bitIndex, int amount = 8)
		{
			bytes.Write((uint)value, ref bitIndex, amount);
		}
		#endregion
		public static void Write(this byte[] bytes, uint value, ref uint bitIndex, int numBitsToWrite)
		{
			if(numBitsToWrite > 32 || numBitsToWrite == 0) throw new ArgumentOutOfRangeException("numBitsToWrite must be at least 1 and no greater than 32, but it was: " + numBitsToWrite);
			
			int minRequiredLength = ( ((int)bitIndex + numBitsToWrite - 1) >> 3 ) + 1;
			if(minRequiredLength > bytes.Length)
			{
				throw new ArgumentOutOfRangeException("numBitsToRead", "bitIndex out of bounds. " + 
				"Could not write " + numBitsToWrite + " bits to a byte array of bit length " + (bytes.Length << 3) +
				" and at bit index: " + bitIndex + ". Only " + ((bytes.Length << 3) - bitIndex) + " bits left.");
			}
			
			for(uint i = 0; i < 5; i++)
			{
				uint byteIndex = bitIndex >> 3;
				uint bitIndexInByte = bitIndex & 0x7;
				uint numBitsCanBeWritten = 8-bitIndexInByte;
				
				int readStart = numBitsToWrite;
				int readEnd = (int)(numBitsToWrite <= 8 ? 0 : numBitsToWrite - numBitsCanBeWritten);
				
				byte byteToWrite = (byte)((value >> readEnd) & ((1 << (readStart - readEnd)) - 1));
				
				if(readEnd > 0)
				{
					bytes[byteIndex] |= byteToWrite;
				}
				else
				{
					int shift = (int)(numBitsCanBeWritten - readStart);
					if(shift > 0) bytes[byteIndex] |= (byte)(byteToWrite << shift);
					else 		  bytes[byteIndex] |= (byte)(byteToWrite >> -shift);
				}
				
				bitIndex += (uint)((readStart - readEnd).Min((int)numBitsCanBeWritten));

				if(readEnd == 0 && numBitsToWrite <= numBitsCanBeWritten) return;
				
				numBitsToWrite -= (int)numBitsCanBeWritten;
			}
		}
		
		public static byte[] ReadBits(this byte[] bytes, ref uint bitIndex, int numBitsToRead)
		{
			if(numBitsToRead == 0) throw new ArgumentOutOfRangeException("numBitsToRead", "numBitsToRead must be 1 or greater. But it is: " + numBitsToRead);
			int byteAmount = 1 + ((numBitsToRead - 1) >> 3);
			byte[] values = new byte[byteAmount];
			byteAmount--;
			for(int i = 0; i < byteAmount; i++)
			{
				values[i] = bytes.ReadByte(ref bitIndex);
			}
			numBitsToRead &= 0x7;
			if(numBitsToRead == 0) numBitsToRead = 8; 
			values[byteAmount] = bytes.ReadByte(ref bitIndex, numBitsToRead);
			return values;
		}
		public static bool ReadBool(this byte[] bytes, ref uint bitIndex)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, 1);
			return temp > 0;
		}
		public static byte ReadByte(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 8)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return BitConverter.GetBytes(temp)[0];
		}
		public static short ReadShort(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 16)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return BitConverter.ToInt16(BitConverter.GetBytes(temp), 0);
		}
		public static ushort ReadUshort(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 16)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return BitConverter.ToUInt16(BitConverter.GetBytes(temp), 0);
		}
		public static int ReadInt(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 32)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return BitConverter.ToInt32(BitConverter.GetBytes(temp), 0);
		}
		public static uint ReadUint(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 32)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return temp;
		}
		public static long ReadLong(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 64)
		{
			uint temp1 = 0;
			uint temp2 = 0;
			bytes.Read(ref temp1, ref bitIndex, (numBitsToRead > 32 ? 32 : numBitsToRead));
			if(numBitsToRead > 32) bytes.Read(ref temp2, ref bitIndex, (numBitsToRead - 32));
			ulong temp = (((ulong)temp2) << 32) | ((ulong)temp1);
			return BitConverter.ToInt64(BitConverter.GetBytes(temp), 0);
		}
		public static ulong ReadULong(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 64)
		{
			uint temp1 = 0;
			uint temp2 = 0;
			bytes.Read(ref temp1, ref bitIndex, (numBitsToRead > 32 ? 32 : numBitsToRead));
			if(numBitsToRead > 32) bytes.Read(ref temp2, ref bitIndex, (numBitsToRead - 32));
			return (((ulong)temp2) << 32) | ((ulong)temp1);
		}
		public static float ReadFloat(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 32)
		{
			uint temp = 0;
			bytes.Read(ref temp, ref bitIndex, numBitsToRead);
			return BitConverter.ToSingle(BitConverter.GetBytes(temp), 0);
		}
		public static double ReadDouble(this byte[] bytes, ref uint bitIndex, int numBitsToRead = 64)
		{
			uint temp1 = 0;
			uint temp2 = 0;
			bytes.Read(ref temp1, ref bitIndex, (numBitsToRead > 32 ? 32 : numBitsToRead));
			if(numBitsToRead > 32) bytes.Read(ref temp2, ref bitIndex, (numBitsToRead - 32));
			ulong temp = (((ulong)temp2) << 32) | ((ulong)temp1);
			return BitConverter.ToDouble(BitConverter.GetBytes(temp), 0);
		}
		public static int[] ReadIntArray(this byte[] bytes, int numBitsPerInt = 32)
		{
			uint counter = 0;
			return bytes.ReadIntArray(ref counter, bytes.Length >> 2, numBitsPerInt);
		}
		public static int[] ReadIntArray(this byte[] bytes, ref uint bitIndex, int length, int numBitsPerInt = 32)
		{
			uint temp = 0;
			int[] array = new int[length];
			for(int i = 0; i < length; i++)
			{
				bytes.Read(ref temp, ref bitIndex, numBitsPerInt);
				array[i] = BitConverter.ToInt32(BitConverter.GetBytes(temp), 0);
			}
			return array;
		}
		public static void Read(this byte[] bytes, ref uint value, ref uint bitIndex, int numBitsToRead)
		{
			if(numBitsToRead > 32 || numBitsToRead == 0) throw new ArgumentOutOfRangeException("numBitsToRead", "numBitsToRead must be at least 1 and no greater than 32, but it was: " + numBitsToRead);
			
			int minRequiredLength = ( ((int)bitIndex + numBitsToRead - 1) >> 3 ) + 1;
			if(minRequiredLength > bytes.Length)
			{
				throw new ArgumentOutOfRangeException("numBitsToRead", "bitIndex out of bounds. " + 
				"Could not read " + numBitsToRead + " bits from byte array of bit length " + (bytes.Length << 3) +
				" and at bit index: " + bitIndex + ". Only " + ((bytes.Length << 3) - bitIndex) + " bits left.");
			}
			
			for(uint i = 0; i < 5; i++)
			{
				uint byteIndex = bitIndex >> 3;
				uint bitIndexInByte = bitIndex & 0x7;
				uint numBitsCanBeRead = 8-bitIndexInByte;
				uint numBitsWillBeRead = numBitsToRead < numBitsCanBeRead ? (uint)numBitsToRead : numBitsCanBeRead;
				
				int readStart = (int)numBitsCanBeRead;
				int readEnd = (int)(readStart - numBitsWillBeRead);
				
				uint byteToRead = (uint)((bytes[byteIndex] >> readEnd) & ((1 << (readStart - readEnd)) - 1));
				
				value |= byteToRead << (int)(numBitsToRead - numBitsWillBeRead);
				
				bitIndex += numBitsWillBeRead;
				numBitsToRead -= (int)numBitsWillBeRead;

				if(numBitsToRead == 0) return;
			}
		}
		public static String ToBitString(this byte[] bytes, uint startBitIndex = 0)
		{
			if((bytes.Length << 3) <= startBitIndex) return "";
			if(startBitIndex > 0) bytes = bytes.ReadBits(ref startBitIndex, (bytes.Length << 3) - (int)startBitIndex);
			
			int length = bytes.Length;
			if(length == 0) return "";
			
			System.Text.StringBuilder builder = new System.Text.StringBuilder(length + (length << 3) - 1);
			
			int index = 0;
			byte val = bytes[0];
			for(int j = 0; j < 8; j++)
			{
				builder.Insert(j, (val >> (7-j)) & 0x1);
			}
			
			for(int i = 1; i < length; i++)
			{
				
				index = i + (i << 3);
				
				builder.Insert(index-1, ' ');
				
				val = bytes[i];
				for(int j = 0; j < 8; j++)
				{
					builder.Insert(index + j, (val >> (7-j)) & 0x1);
				}
			}
			return builder.ToString();
		}
		public static String ToHexString(this byte[] bytes, uint startBitIndex = 0)
		{
			if((bytes.Length << 3) <= startBitIndex) return "";
			if(startBitIndex > 0) bytes = bytes.ReadBits(ref startBitIndex, (bytes.Length << 3) - (int)startBitIndex);
			
			int length = bytes.Length;
			if(length == 0) return "";
			
			System.Text.StringBuilder builder = new System.Text.StringBuilder(length + (length << 1) - 1);
			
			builder.Append(bytes[0].ToString("X4"), 2, 2);
			
			for(int i = 1; i < length; i++)
			{
				
				if((i & 0x3) > 0) builder.Append(' ');
				else if((i & 0xF) > 0) builder.Append('\t');
				else builder.Append('\n');
				
				builder.Append(bytes[i].ToString("X4"), 2, 2);
			}
			return builder.ToString();
		}
	}
}

